﻿using System;
using System.ComponentModel;
using System.Reflection;

namespace Moonlite.Data.Configuration
{
    /// <summary>
    /// Represents a class that automatically syncs its properties with their targets.
    /// </summary>
    public class ConfigurationPropertySyncer : INotifyPropertyChanged
    {
        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises the property changed event.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        protected void RaisePropertyChangedEvent(string propertyName)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// Raises the <see cref="E:PropertyChanged"/> event.
        /// </summary>
        /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            PropertyInfo property = GetType().GetProperty(e.PropertyName);
            if (property == null)
            {
                throw new InvalidOperationException("The specified property does not exist.");
            }

            Type configurationPropertyAttributeType = typeof(ConfigurationPropertyAttribute);
            bool hasConfigurationPropertyAttribute = Attribute.IsDefined(property, configurationPropertyAttributeType);
            if (hasConfigurationPropertyAttribute)
            {
                object[] attributes = property.GetCustomAttributes(configurationPropertyAttributeType, false);
                if (attributes != null && attributes.Length > 0)
                {
                    ConfigurationPropertyAttribute configurationPropertyAttribute = (ConfigurationPropertyAttribute)attributes[0];
                    if (configurationPropertyAttribute != null)
                    {
                        if (configurationPropertyAttribute.TargetType != null)
                        {
                            PropertyInfo targetProperty = configurationPropertyAttribute.TargetType.GetProperty(configurationPropertyAttribute.TargetSyncPropertyName);
                            if (targetProperty == null)
                            {
                                throw new InvalidOperationException(string.Format("The sync property '{0}' does not exist on type '{1}'", configurationPropertyAttribute.TargetSyncPropertyName, configurationPropertyAttribute.TargetType.FullName));
                            }
                            else
                            {
                                targetProperty.SetValue(null, property.GetValue(this, null), null);
                            }
                        }
                    }
                    else
                    {
                        ExceptionHelper.ThrowConfigurationPropertyAttributeNotFoundException(property.Name);
                    }
                }
            }
            else
            {
                ExceptionHelper.ThrowConfigurationPropertyAttributeNotFoundException(property.Name);
            }

            if (PropertyChanged != null)
            {
                PropertyChanged(this, e);
            }
        }
    }
}
